/* Copyright (C) 2021 Rain */

/* This file is part of XNIX. */

/* 
  XNIX is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  XNIX is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with XNIX. If not, see <https://www.gnu.org/licenses/>.  
*/




#include <kernel/init.h>
#include <kernel/errno.h>
#include <kernel/types.h>
#include <kernel/vsprintf.h>

#include <arch/x86/int.h>
#include <arch/io.h>
#include <arch/sys.h>

#include <stdarg.h>



/* 
 * vsprintf: a function that uses to format strings. 
 *
 * buf:  (write only) buffer for output. 
 * fmt:  formater (read only)
 * args: arguments
 *
 * SUPPORTS:
 *	STANDARD FORMATERS: 
 * 		%c: a character
 * 		%d: a 32-bit 10-based signed   int number. 
 * 		%l: a 64-bit 10-based signed   long number. 
 *	 	%u: a 64-bit 10-based unsigned long number. 
 * 		%x: a 64-bit 16-based unsigned long number. 
 *
 *	OTHERS: 
 *	8 :	%b: a 16-based unsigned char  number. 
 *	16:	%w: a 16-based unsigned short number. 
 *	32:	%d: (defined)
 *	64:	%q: a 16-based unsigned long  number. 
 * */

int vsprintf(char *buf, const char *fmt, va_list args) {
	/* ri: real index in buf */
	__uint64_t i, ri;
	char state, c;

	state = 0;

	for (i = 0, ri = 0; (c = fmt[i]); i++) {
		if (!state) 
			if (c == '%') 
				state = 1;
			else 
				buf[ri++] = c;

		else {
			if (c == 'c') 
				/* gcc compiler makes a char (that 1 byte) to 
				 * a int (4 bytes) in stack and registers because
				 * many registers haven't got a byte-size piece. 
				 * and sp must be 8-bytes aligned in long-mode, 
				 * 4-bytes in protected-mode. 
				 * */
				buf[ri++] = va_arg(args, int);

			else if (c == 's') {
				/* it's not so difficult, I think */

				/* get pointer and save in s */
				const char *s = va_arg(args, char *);

				while (*s) {
					buf[ri++] = *s;
					s++;
				}
			}

			else if (c == 'd') 
				ri += itos(buf + ri, va_arg(args, int), 10, 1, 0, 0);

			else if (c == 'l') 
				ri += itos(buf + ri, va_arg(args, long), 10, 1, 0, 0);

			else if (c == 'u') 
				ri += itos(buf + ri, va_arg(args, unsigned long), 10, 1, 0, 0);

			else if (c == 'x') 
				ri += itos(buf + ri, va_arg(args, unsigned long), 16, 0, 0, 0);

			else if (c == 'b') 
				ri += itos(buf + ri, va_arg(args, unsigned int), 16, 0, 1, 2);

			else if (c == 'w') 
				ri += itos(buf + ri, va_arg(args, unsigned int), 16, 0, 1, 4);

			else if (c == 'q') 
				ri += itos(buf + ri, va_arg(args, unsigned long), 16, 0, 1, 16);

			else {
				/* unsupported format, we should put '%' on screen too */
				buf[ri++] = '%';
				buf[ri++] = c;
			}

			state = 0;
		}
	}

	return ri;
}



int itos(char *buffer, unsigned long n, int base, int sign, int withzero, int width) {
	static char digits[] = "0123456789abcdef";

	char tbuf[16] = {0};
	int i, neg, j;

	__uint64_t x;

	if (sign && n < 0) {
		neg = 1;
		x = -n;
	}

	else {
		neg = 0;
		x = n;
	}

	i = 0;

	do {
		tbuf[i++] = digits[x % base];
	} while (x /= base);

	if (withzero && base == 16) {
		for (i = 0; i < width; i++) {
			if (!tbuf[i])
				tbuf[i] = '0';
		}
	}

	if (neg) 
		tbuf[i++] = '-';

	j = i;

	while (--j >= 0) 
		buffer[i - j - 1] = tbuf[j];

	return i;
}
